운영 지표에서 평균보다 백분위수를 보는 이유

운영 지표에서 평균보다 백분위수를 보는 이유

한눈에 보기

평균 지연 시간은 서비스의 전체적인 변화를 보는 데 유용하지만 사용자가 실제로 겪는 느린 요청의 규모를 설명하지 못한다. p50은 전형적인 경험을, p95와 p99는 분포의 긴 꼬리를 보여 준다. 다만 백분위수도 표본 수, 집계 범위, 히스토그램 버킷, 트래픽 비중을 무시하면 쉽게 오해할 수 있다.

목차

평균 하나로는 보이지 않는 장애

API 지연 시간을 관측할 때 가장 먼저 떠올리는 값은 평균이다. 모든 요청 시간을 더한 뒤 요청 수로 나누면 되므로 계산과 설명이 쉽다.

average latency
  = sum of all request durations
  / number of requests

하지만 운영 환경의 지연 시간은 정규 분포처럼 평균 주변에 고르게 모이지 않는 경우가 많다. 대부분의 요청은 빠르지만 일부 요청이 데이터베이스 잠금, 캐시 미스, 네트워크 재시도, GC pause 때문에 매우 느려지는 긴 꼬리 분포를 만든다.

두 서비스의 요청 100개를 단순화해 보자.

Service A
- 100 requests × 100ms
- average = 100ms

Service B
- 99 requests × 50ms
- 1 request × 5,050ms
- average = 100ms

두 서비스의 평균은 모두 100ms다. 하지만 사용자 경험은 다르다.

평균만 있는 대시보드에서는 두 시스템이 똑같아 보인다. 더 나쁜 경우도 있다. 느린 요청 비율이 0.5%에서 1%로 두 배가 되어도 빠른 요청이 충분히 많으면 평균선은 거의 움직이지 않는다.

평균이 틀린 값이라는 뜻은 아니다

평균은 총 작업 시간, 자원 사용량, 전체적인 성능 변화를 설명하는 데 중요하다. 다만 평균 하나로 분포의 모양과 꼬리 사용자 경험까지 설명하려는 것이 문제다.

백분위수는 무엇을 뜻할까

p95의 p는 percentile, 숫자는 백분위 위치를 뜻한다. 관측값을 작은 순서대로 나열했을 때 p95는 대략 95%의 관측값이 그 값 이하에 있다는 의미다.

p50 = 50%의 요청이 이 값 이하
p95 = 95%의 요청이 이 값 이하
p99 = 99%의 요청이 이 값 이하

p50은 중앙값과 대응한다. p99가 800ms라면 관측 구간의 요청 중 약 99%가 800ms 이내에 끝났고, 나머지 약 1%는 그보다 느렸다고 해석할 수 있다.

흔한 오해

p99가 “가장 느린 요청이 800ms”라는 뜻은 아니다. p99 바깥의 1%는 801ms일 수도 있고 30초일 수도 있다. 최댓값이나 timeout 비율을 별도로 봐야 한다.

작은 배열에서 nearest-rank 방식으로 백분위수를 구하는 예를 보자. 실제 모니터링 시스템은 보간법과 추정 알고리즘이 다를 수 있으므로 이 코드는 개념 설명용이다.

function nearestRankPercentile(values: number[], percentile: number): number {
  if (values.length === 0) {
    throw new Error("at least one observation is required");
  }

  if (percentile <= 0 || percentile > 1) {
    throw new Error("percentile must be in the range (0, 1]");
  }

  const sorted = [...values].sort((a, b) => a - b);
  const rank = Math.ceil(percentile * sorted.length);

  return sorted[rank - 1];
}

const samples = [40, 42, 45, 48, 50, 52, 60, 80, 900, 1_500];

console.log(nearestRankPercentile(samples, 0.5)); // 50
console.log(nearestRankPercentile(samples, 0.9)); // 900
console.log(nearestRankPercentile(samples, 0.99)); // 1500

표본이 10개뿐이면 p99는 사실상 최댓값과 같다. 높은 백분위수는 충분한 표본이 있을 때 의미가 커진다. 초당 1건인 API의 1분 p99와 초당 10,000건인 API의 1분 p99는 통계적인 안정성이 전혀 다르다.

p50 p95 p99를 함께 읽는 방법

백분위수 하나도 충분하지 않다. 서로 다른 위치를 함께 봐야 분포의 모양을 짐작할 수 있다.

지표 주로 보여 주는 것 놓칠 수 있는 것
p50 전형적인 요청 경험 느린 사용자 집단
p90 비교적 흔한 느린 요청 더 희귀한 꼬리
p95 상위 5%의 경계 1% 이하의 극단값
p99 심한 tail latency 극소수 timeout과 최댓값
평균 전체 작업량의 중심 분포의 모양
최대 관측된 최악의 값 일회성 이상치 여부

예를 들어 다음 변화는 서로 다른 원인을 암시한다.

Case 1
p50:  80ms → 180ms
p95: 200ms → 300ms
p99: 500ms → 600ms

Case 2
p50:  80ms →  82ms
p95: 200ms → 210ms
p99: 500ms → 3,000ms

Case 1은 전체 요청 경로가 전반적으로 느려진 모습이다. 공통 미들웨어 추가, CPU 포화, 데이터베이스 기본 쿼리의 퇴행을 의심할 수 있다.

Case 2는 대부분의 요청은 그대로지만 극단적인 일부만 느려졌다. 특정 shard, 특정 고객 데이터 크기, 캐시 미스, 재시도, lock contention 같은 조건부 문제일 가능성이 높다.

flowchart LR
    A[latency 변화 발견] --> B{p50도 상승했는가}
    B -- 예 --> C[공통 요청 경로와 전체 포화 조사]
    B -- 아니오 --> D{p95 또는 p99만 상승했는가}
    D -- 예 --> E[조건부 경로·특정 의존성·재시도 조사]
    D -- 아니오 --> F[표본 수와 계측 오류 확인]

백분위수 사이의 간격도 유용하다. p99 / p50 비율이 갑자기 커졌다면 분포의 꼬리가 길어진 것이다. 다만 이 비율만으로 알림을 만들기보다 실제 SLO 경계와 요청 수를 함께 본다.

긴 꼬리가 사용자 경험에 미치는 영향

한 화면을 열기 위해 API를 한 번만 호출한다면 p99는 요청의 1%에 해당한다. 그러나 한 사용자 작업이 여러 하위 요청에 의존하면 적어도 하나의 느린 요청을 만날 확률이 커진다.

각 요청이 서로 독립이고 요청 하나가 p99 경계를 넘을 확률이 1%라고 단순화하자. 화면이 병렬 API 20개 중 가장 느린 응답을 기다린다면 적어도 하나가 꼬리에 걸릴 확률은 다음과 같다.

P(at least one slow request)
  = 1 - P(all requests are fast)
  = 1 - 0.99^20
  ≈ 18.2%
function probabilityOfAtLeastOneSlowRequest(
  slowProbability: number,
  requestCount: number,
): number {
  return 1 - (1 - slowProbability) ** requestCount;
}

console.log(probabilityOfAtLeastOneSlowRequest(0.01, 20));
// 약 0.182

실제 요청은 독립적이지 않을 수 있다. 같은 데이터베이스나 네트워크를 공유하면 동시에 느려질 가능성이 높아져 상황은 더 나빠진다. fan-out 구조에서 tail latency를 중요하게 보는 이유다.

느린 요청은 단순히 해당 사용자 한 명에게만 영향을 주지 않는다.

  1. 클라이언트나 프록시가 timeout 전에 재시도한다.
  2. 원래 요청은 서버에서 계속 실행될 수 있다.
  3. 추가 요청이 connection pool과 worker를 점유한다.
  4. queue가 길어져 원래 빠르던 요청도 느려진다.
  5. 더 많은 재시도가 발생한다.
flowchart LR
    A[일부 요청 지연] --> B[클라이언트 재시도]
    B --> C[동시 요청 증가]
    C --> D[pool·worker 포화]
    D --> E[더 많은 요청 지연]
    E --> B

이 양의 피드백을 막으려면 p99만 보는 데서 끝나지 않고 timeout, retry budget, concurrency limit, cancellation 전파를 함께 설계해야 한다.

평균과 백분위수 중 하나만 고르지 않는다

“평균 대신 p95를 보자”는 문장은 방향은 맞지만 둘 중 하나를 버리라는 뜻으로 받아들이기 쉽다. 각 지표는 다른 질문에 답한다.

average = sum / count

평균은 여러 인스턴스에서 수집한 sumcount를 더해 정확하게 다시 계산할 수 있다. 전체 CPU 작업 시간이나 원본에 가해진 총 부하와도 연결하기 쉽다.

백분위수는 분포의 위치를 알려 주지만 총 작업량을 설명하지 않는다. p99가 매우 높아도 요청 수가 10건뿐일 수 있고, p99가 낮아도 초당 수십만 건의 요청이 CPU를 포화시킬 수 있다.

운영 대시보드에서는 보통 다음 조합이 유용하다.

RED Method의 Rate, Errors, Duration도 이 조합을 단순하게 시작하는 방법이다. 구체적인 대시보드 구성은 RED Method로 API 모니터링 시작하기에서 이어서 다룬다.

히스토그램으로 분포를 저장하는 이유

모든 요청 지연 시간을 원본 값 그대로 장기간 저장하면 비용이 크다. 모니터링 시스템은 흔히 관측값을 구간별로 세는 히스토그램을 사용한다.

다음 8개 요청을 생각해 보자.

0.04s, 0.08s, 0.12s, 0.18s, 0.24s, 0.27s, 0.80s, 1.70s

누적 히스토그램 버킷은 다음과 같이 표현할 수 있다.

상한 le 누적 요청 수
0.05 1
0.10 2
0.25 5
0.50 6
1.00 7
+Inf 8

le="0.25" 값 5는 0.25초 이하 요청이 5개라는 뜻이다. 각 버킷이 독립 구간의 개수가 아니라 누적 개수라는 점이 중요하다.

http_request_duration_seconds_bucket{le="0.05"} 1
http_request_duration_seconds_bucket{le="0.10"} 2
http_request_duration_seconds_bucket{le="0.25"} 5
http_request_duration_seconds_bucket{le="0.50"} 6
http_request_duration_seconds_bucket{le="1.00"} 7
http_request_duration_seconds_bucket{le="+Inf"} 8
http_request_duration_seconds_sum 3.43
http_request_duration_seconds_count 8

히스토그램은 원본 관측값을 정확하게 보존하지 않는다. 대신 여러 인스턴스와 시간 구간의 버킷 카운트를 합칠 수 있다. 이 집계 가능성이 운영 지표에서 매우 중요하다.

Prometheus 히스토그램 계측 예제

다음은 특정 서비스의 실제 코드를 옮긴 것이 아니라 Node.js API를 가정해 재구성한 예시다.

import { Histogram } from "prom-client";

const requestDurationSeconds = new Histogram({
  name: "http_server_request_duration_seconds",
  help: "Duration of completed HTTP server requests",
  labelNames: ["method", "route", "status_class"] as const,
  buckets: [0.025, 0.05, 0.1, 0.2, 0.3, 0.5, 1, 2, 5],
});

function statusClass(statusCode: number): string {
  return `${Math.floor(statusCode / 100)}xx`;
}

요청 시작 시 타이머를 만들고 완료 시 라벨과 함께 관측한다.

type ResponseLike = {
  statusCode: number;
  once(event: "finish", listener: () => void): void;
};

function observeRequest(
  method: string,
  routeTemplate: string,
  response: ResponseLike,
): void {
  const stopTimer = requestDurationSeconds.startTimer();

  response.once("finish", () => {
    stopTimer({
      method,
      route: routeTemplate,
      status_class: statusClass(response.statusCode),
    });
  });
}

여기서 route에는 실제 URL이 아니라 /articles/:id 같은 템플릿을 넣어야 한다.

// 나쁜 예: article ID마다 새 시계열 생성
route = request.path; // /articles/91482

// 좋은 예: 제한된 라우트 템플릿 사용
route = matchedRoute.pattern; // /articles/:id

사용자 ID, 요청 ID, 원본 URL을 라벨에 넣으면 시계열 카디널리티가 폭발한다. 느린 개별 요청을 찾는 일은 metric label 대신 trace ID와 exemplar, 구조화 로그로 연결한다.

classic histogram에서 최근 5분의 p95를 전체 인스턴스 기준으로 계산하는 PromQL은 다음 형태다.

histogram_quantile(
  0.95,
  sum by (le) (
    rate(http_server_request_duration_seconds_bucket[5m])
  )
)

라우트별 p99를 보려면 집계 차원에 route를 남긴다.

histogram_quantile(
  0.99,
  sum by (route, le) (
    rate(http_server_request_duration_seconds_bucket[10m])
  )
)

평균도 같은 히스토그램의 sumcount로 계산할 수 있다.

sum(rate(http_server_request_duration_seconds_sum[5m]))
/
sum(rate(http_server_request_duration_seconds_count[5m]))

특정 SLO 경계, 예를 들어 300ms 이하 요청 비율은 백분위수보다 직접 계산하는 편이 명확하다.

sum(rate(http_server_request_duration_seconds_bucket{le="0.3"}[5m]))
/
sum(rate(http_server_request_duration_seconds_count[5m]))

이 식이 0.97이라면 최근 5분 요청의 약 97%가 300ms 이내였다는 뜻이다. “p95가 몇 ms인가”와 “300ms 목표를 만족한 요청 비율이 얼마인가”는 비슷해 보이지만 서로 다른 질문이다.

버킷 경계가 정확도를 결정한다

classic histogram의 백분위수는 버킷 안의 정확한 관측값을 알지 못한다. histogram_quantile()은 백분위 위치가 들어 있는 버킷 내부를 보간해 추정한다. 따라서 넓은 버킷에서는 오차가 커질 수 있다.

SLO가 300ms인데 버킷이 다음처럼 설정되어 있다고 하자.

0.1s, 0.5s, 1s, 5s

p95가 0.1초와 0.5초 사이에 위치하면 300ms 근처에서 정확한 판단을 하기 어렵다. 목표 경계 주변에 버킷을 더 촘촘히 둔다.

0.05s, 0.1s, 0.2s, 0.3s, 0.45s, 0.75s, 1s, 2s, 5s

버킷은 무조건 많다고 좋은 것도 아니다. classic histogram에서는 라벨 조합마다 버킷 수만큼 시계열이 늘어난다.

time series count
  ≈ routes
  × methods
  × status classes
  × instances
  × buckets

라우트 40개, method 4개, 상태 등급 5개, 인스턴스 20개, 버킷 12개라면 버킷 시계열만 최대 192,000개 조합이 생길 수 있다. 실제 조합은 더 적더라도 설정 비용을 추정해야 한다.

버킷을 정할 때는 다음 자료를 본다.

  1. 사용자가 체감하거나 SLO로 약속한 지연 경계
  2. 클라이언트와 프록시 timeout
  3. 평상시 p50과 p99의 범위
  4. batch API처럼 긴 요청이 섞이는지
  5. 저장 비용과 라벨 조합 수

Prometheus가 지원하고 운영 환경에서 안정적으로 사용할 수 있다면 native histogram도 검토할 수 있다. native histogram은 해상도 설정을 기반으로 더 유연하게 집계할 수 있지만, 서버·클라이언트 라이브러리·원격 저장소의 지원 상태를 함께 확인해야 한다.

백분위수는 추정값이다

히스토그램에서 계산한 p95가 0.298s라고 해서 모든 원본 관측값을 마이크로초 단위로 알고 있다는 뜻은 아니다. 버킷 해상도와 보간 방식이 만든 추정치다.

백분위수를 평균 내면 안 된다

인스턴스 A와 B가 각각 p95를 노출한다고 하자.

instance A: 100 requests, p95=100ms
instance B: 10,000 requests, p95=900ms

두 값을 단순 평균 내면 500ms다.

(100ms + 900ms) / 2 = 500ms

하지만 인스턴스별 요청 수가 크게 다르므로 전체 요청의 p95는 500ms라고 할 수 없다. 가중 평균을 내도 백분위수는 일반적으로 복원되지 않는다. 각 분포의 원본 정보가 사라졌기 때문이다.

# 잘못된 예: 인스턴스별로 미리 계산된 p95의 평균
avg(api_request_duration_seconds{quantile="0.95"})

classic histogram에서는 먼저 같은 경계의 버킷 카운트를 합친 뒤 전체 분포에서 백분위수를 계산한다.

# 올바른 방향: 버킷을 합친 후 p95 추정
histogram_quantile(
  0.95,
  sum by (le) (
    rate(api_request_duration_seconds_bucket[5m])
  )
)

클라이언트에서 미리 quantile을 계산하는 summary는 개별 인스턴스 값은 정확할 수 있지만, 여러 인스턴스의 quantile을 사후 집계할 수 없다는 제약이 있다. 반면 histogram은 버킷 경계가 호환된다는 전제에서 집계할 수 있다.

방식 장점 중요한 제약
평균 sum/count 정확하게 집계 가능 꼬리 분포를 숨김
Summary quantile 클라이언트에서 계산 인스턴스 간 quantile 집계 불가
Classic histogram 버킷 합산 가능 버킷 경계에 따른 추정 오차와 시계열 비용
Native histogram 집계와 해상도에 유리 생태계 지원과 저장 비용 확인 필요

표본 수와 시간 구간을 함께 본다

p99를 읽을 때는 반드시 같은 구간의 요청 수를 확인한다.

API A: 5분간 100,000건, p99=800ms
API B: 5분간 20건,      p99=800ms

숫자는 같지만 의미는 다르다. A에서는 약 1,000건이 p99 바깥에 있을 수 있다. B에서는 p99가 한두 관측값에 크게 흔들리고 사실상 최댓값에 가까울 수 있다.

낮은 트래픽 서비스에서 짧은 창의 고백분위수 알림은 깜빡일 수 있다. 해결책은 상황에 따라 다르다.

(
  histogram_quantile(
    0.99,
    sum by (le) (
      rate(http_server_request_duration_seconds_bucket[10m])
    )
  ) > 1
)
and
(
  sum(rate(http_server_request_duration_seconds_count[10m])) > 5
)

위 식은 초당 평균 5건보다 많은 경우에만 최근 10분 p99 1초 초과를 본다는 개념 예시다. 실제 임곗값은 서비스 트래픽과 SLO에 맞춰야 한다.

시간 창도 해석을 바꾼다. 1분 p99는 빠르게 반응하지만 변동이 크고, 1시간 p99는 안정적이지만 이미 끝난 장애가 오래 남는다. 대시보드에는 짧은 창과 긴 창을 함께 두고, 알림은 error budget burn rate 같은 지속 조건과 연결하는 편이 낫다.

엔드포인트와 의존성을 분리해 해석한다

모든 HTTP 요청을 하나로 합친 p99도 평균과 비슷한 함정이 있다.

GET /health       10,000 RPS, 5ms
POST /checkout        10 RPS, 900ms

트래픽 대부분이 매우 빠른 /health라면 전체 p99에서 /checkout의 문제를 볼 수 없을 수 있다. 사용자 여정과 중요도에 따라 라우트 또는 작업 유형을 구분해야 한다.

그러나 모든 URL을 라벨로 넣으면 카디널리티가 커진다. 다음 정도의 제한된 차원을 사용한다.

응답 성공과 실패를 섞을 때도 주의한다. 실패 응답이 정상 응답보다 매우 빠르면 전체 p95가 좋아진 것처럼 보일 수 있다.

정상 응답: 평균 400ms
즉시 실패: 평균 5ms

장애 후 실패 응답 비율 증가
→ 전체 latency 지표는 오히려 감소할 수 있음

따라서 latency는 성공 여부와 함께 읽는다. 다만 대시보드 패널을 무한히 분할하기보다 RED 지표와 대표 라우트, SLO 기반 뷰를 먼저 만든다.

의존성 지연도 구분한다.

http_server_request_duration_seconds
db_client_query_duration_seconds
cache_client_request_duration_seconds
external_api_request_duration_seconds

외부 API의 p99가 먼저 상승하고 서버 p99가 뒤따랐다면 원인 후보가 좁아진다. 분산 trace의 critical path와 연결하면 해당 꼬리 요청이 어느 span에서 시간을 썼는지 확인할 수 있다.

대시보드와 알림을 설계하는 방법

첫 API 대시보드는 화려한 그래프보다 질문에 답할 수 있어야 한다.

패널 답하려는 질문
요청률 트래픽이 평소와 다른가
오류율 사용자 요청이 실패하는가
p50/p95/p99 전형적 경험과 꼬리가 어떻게 달라졌는가
SLO 경계 이하 비율 약속한 속도를 지키는가
요청 수 백분위수를 믿을 표본이 있는가
인스턴스별 heatmap 특정 인스턴스나 분포 모양이 다른가
배포 annotation 변화가 배포 시점과 겹치는가

백분위수 그래프는 p50, p95, p99를 같은 패널에 그리되 단위와 축을 명확히 한다. p99가 커서 p50이 바닥에 붙는다면 로그 축이나 별도 패널을 고려한다. heatmap은 여러 선만으로 보기 어려운 이중 봉우리와 분포 이동을 찾는 데 유용하다.

알림을 단순히 p99 > 1s 하나로 만들면 다음 문제가 생긴다.

더 나은 알림은 사용자 목표와 연결한다.

SLI = 300ms 이내에 성공한 요청 / 유효한 전체 요청
SLO = 30일 동안 SLI 99.0% 이상

빠른 burn-rate 창과 느린 burn-rate 창을 조합하면 급격한 장애와 서서히 진행되는 퇴행을 구분할 수 있다. 알림이 울렸을 때는 어떤 라우트와 지역이 budget을 태우는지 바로 drill-down할 수 있어야 한다.

p99 그래프는 조사 도구, SLO는 의사결정 기준

“p99가 올랐다”만으로 사용자 약속이 깨졌다고 단정하지 않는다. 서비스가 정의한 good event 기준과 error budget 소모를 함께 본다.

실전 조사 시나리오

배포 직후 전체 평균은 120ms로 그대로인데 p99가 600ms에서 2.8초로 올랐다고 하자. 다음 순서로 조사할 수 있다.

1. 계측과 표본을 확인한다

sum(rate(http_server_request_duration_seconds_count[5m]))

요청 수가 충분한지, 새 배포에서 metric name이나 bucket이 바뀌지 않았는지 확인한다. 서로 다른 버킷 경계가 섞이면 집계가 왜곡될 수 있다.

2. 차원을 제한적으로 나눈다

topk(
  10,
  histogram_quantile(
    0.99,
    sum by (route, le) (
      rate(http_server_request_duration_seconds_bucket[5m])
    )
  )
)

특정 /orders/:id 라우트만 상승했는지 확인한다. 이어 상태 등급, region, 배포 version으로 나누되 한 번에 모든 라벨을 펼쳐 노이즈를 만들지는 않는다.

3. SLO 경계 초과 비율을 확인한다

1 -
(
  sum(rate(http_server_request_duration_seconds_bucket{le="0.3"}[5m]))
  /
  sum(rate(http_server_request_duration_seconds_count[5m]))
)

p99 숫자가 커졌더라도 300ms 초과 요청 비율과 절대 건수가 얼마나 변했는지 본다.

4. 의존성 지표와 trace를 연결한다

histogram_quantile(
  0.99,
  sum by (dependency, le) (
    rate(outbound_request_duration_seconds_bucket[5m])
  )
)

데이터베이스, 캐시, 외부 API 중 어느 분포가 같은 시점에 변했는지 본다. 느린 요청의 trace에서 retry와 queue wait가 critical path를 늘렸는지 확인한다.

5. 배포 차이를 검증한다

old version p99: 620ms
new version p99: 2.8s
new version p50: 84ms

신버전에서 p50은 그대로이고 p99만 올랐다면 모든 요청에 추가된 연산보다 특정 조건의 새 분기나 캐시 미스를 의심한다. 카나리 트래픽을 줄였을 때 꼬리 지연과 SLO 초과 비율이 회복하는지 본다.

이 과정은 “평균은 정상인데 p99가 나쁘다”에서 멈추지 않고, 어떤 사용자 조건과 요청 경로가 분포를 바꿨는지 좁혀 가는 조사다.

마무리

평균은 모든 관측값을 하나로 압축한다. 그 결과 총체적인 변화는 볼 수 있지만, 요청이 어떤 모양으로 퍼져 있는지는 잃는다. 백분위수는 그 분포의 서로 다른 위치를 보여 준다.

p50은 전형적인 경험, p95와 p99는 꼬리 경험, 평균은 전체 작업량을 설명한다. 이 값들을 요청 수와 SLO 경계 초과 비율과 함께 볼 때 비로소 운영 상황을 제대로 읽을 수 있다.

실무에서 기억할 기준은 다음과 같다.

  1. 평균을 버리지 말고 p50·p95·p99와 함께 본다.
  2. p99는 최댓값이 아니며 바깥 1%의 심각도를 말해 주지 않는다.
  3. 높은 백분위수일수록 표본 수와 시간 창을 확인한다.
  4. 인스턴스별 quantile을 평균 내지 않는다.
  5. 집계가 필요하면 적절한 histogram을 사용한다.
  6. SLO 경계 주변에 정확도가 나오도록 버킷을 설계한다.
  7. route label은 템플릿으로 제한해 카디널리티를 통제한다.
  8. 백분위수 변화는 오류율, 처리율, trace와 함께 조사한다.

평균이 안정적이라고 서비스가 안정적인 것은 아니다. 사용자는 평균 사용자가 아니라 각각의 요청을 경험한다. 그중 느린 요청이 어디에서 얼마나 자주 생기는지 보기 위해 분포를 관측해야 한다.

참고 자료

관련 노트